#include "Pager.h"
#include "Page.h"
#include "Cache.h"

Pager::Pager(const std::string & fileName, const int64_t cacheSize, const int32_t pageSize)
: fileName(fileName)
, pageSize(pageSize)
, file(nullptr)
, cache( new Cache(cacheSize, pageSize))
{
}
Pager::~Pager(){
    if(cache != nullptr){
        delete cache;
        cache = nullptr;
    }
}

int Pager::assertFileExists(){
    //call boost::fs to check & create file
    auto of = new std::ofstream(fileName.c_str(), std::fstream::out|std::fstream::app); 
    if(of != nullptr && *of){
        of->close();
        return 0;
    }
    return -1;
}
int Pager::open(){
    //TODO: open file for read & write  & binary
    // if failed =>  file ptr = nullptr
    // then cache init
    if( 0 != assertFileExists()){  // will move to first savePoint
        return -1; //file not existing....
    }

    file = new std::fstream(fileName.c_str(), std::fstream::in | std::fstream::out | std::fstream::binary);
    if(file == nullptr || !file->good()){
        return -1;//open file failed
    }

    if(0 != cache->init()){
        return -1;//Cache init failed
    }
    return 0;
}

void Pager::close(){
    savePoint();

    if(cache != nullptr){
        cache->deinit();
    }

    if(file != nullptr){
        if(file->is_open())
            file->close();

        delete file;
        file = nullptr;
    }
}

Page * Pager::allocPage(const PageNumberType pageNo){
    assert(pageNo > 0);
    if(!cache->isAllocated(pageNo)){
        return cache->allocate(pageNo);
    }
    return cache->acquire(pageNo);
}

Page * Pager::acquirePage(const PageNumberType pageNo){
    assert(pageNo > 0);
    return cache->acquire(pageNo);
}

int Pager::releasePage(Page * page){
    assert(page != nullptr);

    return writePage(page);
}

Page * Pager::readPage(const PageNumberType pageNo){
    assert(pageNo > 0);

    Page * page = allocPage(pageNo);
    if(page != nullptr){
        switch(page->getStatus()){
            case Page::Allocated:{
                readFile(pageNo, page->getAddr(), page->getSize());
                page->setStatusSync();
                break;
            }
            case Page::Sync:
                break;
            case Page::Dirty:
                break;
            default:
                //Error: status error!
                return nullptr;
        }
        return page;
    }
    //Error: No memory to allocate in cache
    return nullptr;
}


int Pager::writePage(Page * page){
    assert(page != nullptr);
    assert(cache->isAllocated(page->getNo()));
    switch(page->getStatus()){
        case Page::Allocated:
        case Page::Sync:
            return 0;
        case Page::Dirty:{
            auto it = dirtyMap.find(page->getNo());
            if( it == dirtyMap.end()){
                dirtyMap[page->getNo()] = page;
            }
            return 0;
        }
        default:
            //Error status
            return -1;
    }
}


int Pager::savePoint(){
    if(dirtyMap.size() > 0){
        auto it = dirtyMap.begin();
        for(; it != dirtyMap.end(); ++it){

            if(0 != truncateFile(it->first))
                return -1;//truncate File Failed

            auto page = it->second;
            if(0 != writeFile(page->getNo(), page->getAddr(), page->getSize())){
                return -1; //write File failed 
            }
            page->setStatusSync();
        }
        dirtyMap.clear();
    }
    return 0;
}

int Pager::readFile(const PageNumberType pageNo, char * pageAddr, int length){
    //seek 
    if(seekToPage(pageNo) != 0){
        std::cout << "seek error" << std::endl;
        return -1;
    }
    file->read(pageAddr, length);
    if(!file->good()){
        std::cout << "read failed error" << std::endl;
        return -1;
    }
    if(file->gcount() == length){
        return 0;
    }
    std::cout << "file size error" << std::endl;
    return -1;
}
//pageNo > 0; 1-based
int Pager::seekToPage(const PageNumberType pageNo){
    assert(pageNo > 0);
    file->seekg((pageNo-1) * pageSize, file->beg);
    return file->good() ? 0 : -1;
}
int Pager::writeFile(const PageNumberType pageNo, char * pageAddr, int length){
    if(0 != seekToPage(pageNo))
        return -1;//IO seek failed 

    file->write(pageAddr, length);
    if(!file->good()) 
        return -1; //write file failed

    file->flush();
    return 0;
}
int Pager::truncateFile(PageNumberType pageNo){
    //TODO: truncateFile
    return 0;
}


const std::string Pager::toString() const{
    return std::string("Pager.toString()");
}
